package yawning.mew.bear;

import java.util.HashMap;

import yawning.mew.MewProperty;
import yawning.mew.ProcEffect;
import yawning.mew.Report;
import yawning.mew.BuffsDebuffs.Buff;
import yawning.mew.Report.Stat;
import yawning.mew.bear.BearThreatReport.BearThreatStat;
import yawning.mew.bear.BearThreatSimulator.BearTargetData;
import yawning.mew.sim.Simulation;
import yawning.mew.sim.SimulationEngine;
import yawning.mew.sim.SimulationProc;
import yawning.mew.sim.SimulationTarget;
import yawning.mew.sim.Strategy.IAction;
import yawning.mew.sim.Strategy.IStatus;

public class BearThreatSimulation implements Simulation {
	private static final String DEFAULT_STRATEGY_FILE = "yawning/mew/bear/BearThreatSimulator.script";
	private static final String VERSION_INFO = "x.x.x";

	public enum Action implements IAction {
		NONE,

		DEMORALIZINGROAR,
		FERALFF,
		SKULLBASH,
		FERALCHARGE,

		BERSERK,
		ENRAGE,

		LACERATE,
		MANGLE,
		PULVERIZE,
		MAUL,
		THRASH,
		SWIPE,
		BASH,

		BARKSKIN,
		SURVIVALINSTINCTS,
		FRENZIEDREGENERATION,

		POTION,
		HEROISM,
		UNHOLYFRENZY,
		SHATTERINGTHROW,

		// Swing timer control.
		AUTOATTACK_START,
		AUTOATTACK_STOP
	}

	public static class SimulatorProperty {
		public static final MewProperty ASSUMEEXTERNALBLEED = new MewProperty("Simulator.AssumeExternalBleed", false);	// Mob is always bleeding for purposes of Rend and Tear.
		public static final MewProperty ASSUMEEXTERNALDEMO = new MewProperty("Simulator.AssumeExternalDemo", false);		// Mob is always fully debuffed with the Physcal Daamge reduction debuff.
		public static final MewProperty ASSUMEEXTERNALFF = new MewProperty("Simulator.AssumeExternalFF", false);			// Mob is always fully debuffed with the Major Armor debuff.
		public static final MewProperty ASSUMEEXTERNALMANGLE = new MewProperty("Simulator.AssumeExternalMangle", false);	// Mob is always fully debuffed with the Bleed Damage debuff.
		public static final MewProperty UNLIMITEDRAGE = new MewProperty("Simulator.UnlimitedRage", false);					// Infinite rage.
		public static final MewProperty VENGANCE = new MewProperty("Simulator.Vengeance", false);							// Vengeance support.
		public static final MewProperty REPORTMIT = new MewProperty("Simulator.ReportMitigation", false);					// Report mitigation.
	}

	@Override
	public IStatus initSimulation(SimulationEngine su) {
		BearThreatSimulator state = new BearThreatSimulator(su);
		if (!su.hasStrategy()) su.setStrategy(Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_STRATEGY_FILE));
		return state;
	}

	@Override
	public Report getBaseReport() {
		return new BearThreatReport();
	}

	@Override
	public Report getReport(IStatus status) {
		if (!(status instanceof BearThreatSimulator)) return null;

		BearThreatReport report = new BearThreatReport();
		BearThreatSimulator state = (BearThreatSimulator) status;

		BearTargetData primaryTargetData = (BearTargetData) state.mTargetList.get(0).getTargetData();

		double encounterDuration = state.mSimEngine.getEndTime();
		double totalTPS = 0;

		// Mitigation
		double demoralizingRoarTPS = (state.mDemoralizingRoarAction.mNrDemoHit * 12 * state.mModel.multThreatStance) / encounterDuration;
		report.setResult(BearThreatStat.DEMORALIZING_ROAR_UPTIME, state.mDemoralizingRoarAction.getUpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.INFECTED_WOUNDS_UPTIME, state.mInfectedWoundsEv.getUpTime() / encounterDuration * 100);
		if (state.mModel.mToon.getTalent(BearModel.INFECTEDWOUNDS) != 2) report.mHasValidInfectedWoundsUptime = false;
		if (state.mSavageDefenseEv.mNrPossibleAbsorbs > 0) {
			// Display how many mob swings Savage Defense was actually up for since that's a useful metric.
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_UPTIME, (double) state.mSavageDefenseEv.mNrAbsorbs / state.mSavageDefenseEv.mNrPossibleAbsorbs * 100);	// XXX: NAN
		} else {
			// Either mob damage is totally disabled or the druid got really really lucky.  Switch to the buff uptime.
			// Note: This will skew statistics slightly if it's the one in a few billion chance that you dodged everything.
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_UPTIME, (double) state.mSavageDefenseEv.getUpTime() / encounterDuration * 100);
		}
		report.setResult(BearThreatStat.SAVAGE_DEFENSE_PROCS, state.mSavageDefenseEv.mNrSavageDefenseProcs);
		report.setResult(BearThreatStat.SAVAGE_DEFENSE_ABSORBED, state.mSavageDefenseEv.mDamageAbsorbed);
		if (state.mSavageDefenseEv.mNrSavageDefenseProcs > 0) {
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_AVG_SZ, (double) state.mSavageDefenseEv.mTotalAbsorb / state.mSavageDefenseEv.mNrSavageDefenseProcs);
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_PROC_INTERVAL, encounterDuration / state.mSavageDefenseEv.mNrSavageDefenseProcs);
		} else {
			// Uh, Savage Defense did not proc at all.  Failbear. :(
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_AVG_SZ, 0);
			report.setResult(BearThreatStat.SAVAGE_DEFENSE_PROC_INTERVAL, 0);
		}
		report.setResult(BearThreatStat.BARKSKIN_UPTIME, state.mBarkskinAction.getBarkskinUpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.TIER_12_4PC_UPTIME, state.mBarkskinAction.get4T12UpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.SURVIVAL_INSTINCTS_UPTIME, (double) state.mSurvivalInstinctsAction.getUpTime() / encounterDuration * 100);
		double frenziedRegenTPS = state.mFrenziedRegenerationAction.mFrenziedRegenerationHeal * 0.5 * state.mModel.multThreatStance / encounterDuration;
		report.setResult(BearThreatStat.FRENZIED_REGEN_UPTIME, (double) state.mFrenziedRegenerationAction.getUpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.FRENZIED_REGEN_AVG_SZ, (state.mFrenziedRegenerationAction.mNrHeals > 0) ? (double) state.mFrenziedRegenerationAction.mFrenziedRegenerationHeal / state.mFrenziedRegenerationAction.mNrHeals : 0);
		report.setResult(BearThreatStat.FRENZIED_REGEN_HPS, (double) state.mFrenziedRegenerationAction.mFrenziedRegenerationHeal / encounterDuration);
		report.setResult(BearThreatStat.LOTP_PROCS, state.mLotPEv.mNrLotPProcs);
		report.setResult(BearThreatStat.LOTP_AVG_SZ, (state.mLotPEv.mNrLotPProcs > 0) ? (double) state.mLotPEv.mLotPHeal / state.mLotPEv.mNrLotPProcs : 0);
		report.setResult(BearThreatStat.LOTP_HPS, (double) state.mLotPEv.mLotPHeal / encounterDuration);

		// Mob White Attacks
		double mobWhiteAttackDTPS = state.mMobWhiteDamageTaken / encounterDuration;
		long mobWhiteAttacks = state.mNrMobWhiteMiss + state.mNrMobWhiteDodge + state.mNrMobWhiteCrit + state.mNrMobWhiteHit;
		report.setResult(BearThreatStat.MOB_WHITE_ATTACK_DTPS, mobWhiteAttackDTPS);
		report.setResult(BearThreatStat.MOB_WHITE_ATTACKS, mobWhiteAttacks);
		if (mobWhiteAttacks > 0) {
			report.setResult(BearThreatStat.P_MOB_WHITE_MISS, (double) state.mNrMobWhiteMiss / mobWhiteAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_WHITE_DODGE, (double) state.mNrMobWhiteDodge / mobWhiteAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_WHITE_CRIT, (double) state.mNrMobWhiteCrit / mobWhiteAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_WHITE_HIT, (double) state.mNrMobWhiteHit / mobWhiteAttacks * 100);
			report.setResult(BearThreatStat.MOB_WHITE_INTERVAL, encounterDuration / mobWhiteAttacks);
			report.setResult(BearThreatStat.MOB_WHITE_AVG_DMG, state.mMobWhiteDamageTaken / mobWhiteAttacks);
		} else {
			// This should never happen unless mob damage is disabled somehow, so just suppress the output.
			report.mHasValidMobWhiteDamage = false;
		}

		// Mob Yellow Attacks
		double mobYellowAttackDTPS = state.mMobYellowDamageTaken / encounterDuration;
		long mobYellowAttacks = state.mNrMobYellowMiss + state.mNrMobYellowDodge + state.mNrMobYellowHit;
		report.setResult(BearThreatStat.MOB_YELLOW_ATTACK_DTPS, mobYellowAttackDTPS);
		report.setResult(BearThreatStat.MOB_YELLOW_ATTACKS, mobYellowAttacks);
		if (mobYellowAttacks > 0) {
			report.setResult(BearThreatStat.P_MOB_YELLOW_MISS, (double) state.mNrMobYellowMiss / mobYellowAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_YELLOW_DODGE, (double) state.mNrMobYellowDodge / mobYellowAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_YELLOW_HIT, (double) state.mNrMobYellowHit / mobYellowAttacks * 100);
			report.setResult(BearThreatStat.MOB_YELLOW_INTERVAL, encounterDuration / mobYellowAttacks);
			report.setResult(BearThreatStat.MOB_YELLOW_AVG_DMG, state.mMobYellowDamageTaken / mobYellowAttacks);
		} else {
			// This should never happen unless mob damage is disabled somehow, so just suppress the output.
			report.mHasValidMobYellowDamage = false;
		}

		// Mob Magic Attacks
		double mobMagicAttackDTPS = state.mMobMagicDamageTaken / encounterDuration;
		long mobMagicAttacks = state.mNrMobMagicMiss + state.mNrMobMagicHit;
		report.setResult(BearThreatStat.MOB_MAGIC_ATTACK_DTPS, mobMagicAttackDTPS);
		report.setResult(BearThreatStat.MOB_MAGIC_ATTACKS, mobMagicAttacks);
		if (mobMagicAttacks > 0) {
			report.setResult(BearThreatStat.P_MOB_MAGIC_MISS, (double) state.mNrMobMagicMiss / mobMagicAttacks * 100);
			report.setResult(BearThreatStat.P_MOB_MAGIC_HIT, (double) state.mNrMobMagicHit / mobMagicAttacks * 100);
			report.setResult(BearThreatStat.MOB_MAGIC_INTERVAL, encounterDuration / mobMagicAttacks);
			report.setResult(BearThreatStat.MOB_MAGIC_AVG_DMG, state.mMobMagicDamageTaken / mobMagicAttacks);
		} else {
			// This should never happen unless mob damage is disabled somehow, so just suppress the output.
			report.mHasValidMobMagicDamage = false;
		}

		report.setResult(BearThreatStat.TOTAL_DTPS, mobWhiteAttackDTPS + mobYellowAttackDTPS + mobMagicAttackDTPS);
		if (state.mMobWhiteBaseDamage + state.mMobYellowBaseDamage > 0)
			report.setResult(BearThreatStat.PHYSICAL_DAMAGE_REDUCTION, (1.0f - (double) (state.mMobWhiteDamageTaken + state.mMobYellowDamageTaken) / (state.mMobWhiteBaseDamage + state.mMobYellowBaseDamage)) * 100);
		if (state.mMobMagicBaseDamage > 0)
			report.setResult(BearThreatStat.MAGIC_DAMAGE_REDUCTION, (1.0f - (double) state.mMobMagicDamageTaken / state.mMobMagicBaseDamage) * 100);

		// Skull Bash
		report.setResult(BearThreatStat.NR_TARGET_CASTS, primaryTargetData.getSimulationTarget().getTargetCastEv().getNrCasts() + primaryTargetData.getSimulationTarget().getTargetCastEv().getNrInterruptedCasts());
		report.setResult(BearThreatStat.NR_INTERRUPTED_CASTS, primaryTargetData.getSimulationTarget().getTargetCastEv().getNrInterruptedCasts());
		report.setResult(BearThreatStat.NR_SKULL_BASHES, state.mSkullBashAction.mNrSkullBashHit);

		// Threat boosts
		// XXX: Shattering throw uptime is primary target only.
		report.setResult(BearThreatStat.VENGEANCE_CAP, state.mModel.getVengeanceCap());
		report.setResult(BearThreatStat.VENGEANCE_AVG, state.mVengeanceEv.getAverageVengeance());
		report.setResult(BearThreatStat.KOTJ_ENRAGE_UPTIME, (state.mModel.mToon.getTalent(BearModel.KINGOFTHEJUNGLE) > 0) ? state.mEnrageAction.getUpTime() / encounterDuration * 100 : 0);
		report.setResult(BearThreatStat.STAMPEDE_UPTIME, state.mStampedeEv.getUpTime()/ encounterDuration * 100);
		report.setResult(BearThreatStat.SHATTERING_THROW_UPTIME, primaryTargetData.mShatteringThrowEv.getUpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.HEROISM_UPTIME, state.mHeroismEv.getUpTime() / encounterDuration * 100);
		report.setResult(BearThreatStat.UNHOLY_FRENZY_UPTIME, state.mUnholyFrenzyEv.getUpTime() / encounterDuration * 100);
		if (state.mModel.mBuffsDebuffs.isBuff(Buff.TRICKSOFTHETRADE)) {
			// Sigh.  The TOTT event returns 100% for getUpTime, so fake this.  It's correct anyway.
			report.setResult(BearThreatStat.TOTT_UPTIME, state.mModel.GetCooldownUptime(state.mModel.tottBuffDuration, state.mModel.tottCDDuration, (int) encounterDuration) / encounterDuration * 100);
		} else report.setResult(BearThreatStat.TOTT_UPTIME, 0);

		// Rage
		// XXX: Does rage gain from damage generate threat?  Assuming no for now.
		// XXX: Does OOC procing still generate threat?  Assuming yes for now.
		report.setResult(BearThreatStat.RAGE_SPENT, state.mRageSpent);
		report.setResult(BearThreatStat.RAGE_OVERFLOW, Math.round(state.mRageOverflow));
		if (state.mSimEngine.getConfig().getBool(SimulatorProperty.UNLIMITEDRAGE)) {
			report.mHasValidRageOverflow = false;
		}
		report.setResult(BearThreatStat.RAGE_GAINED_AUTO_ATTACK, state.mMeleeEv.mRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED_NATURAL_REACTION, state.mNaturalReactionRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED_PRIMAL_FURY, state.mPrimalFuryRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED_PRIMAL_MADNESS, state.mPrimalMadnessRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED_ENRAGE, state.mEnrageAction.mRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED_MOB_WHITE, Math.round(state.mMobMeleeRageGained));
		report.setResult(BearThreatStat.RAGE_GAINED_MOB_YELLOW, Math.round(state.mMobYellowRageGained));
		report.setResult(BearThreatStat.RAGE_GAINED_MOB_MAGIC, Math.round(state.mMobMagicRageGained));
		double rageGained = state.mMeleeEv.mRageGained + state.mNaturalReactionRageGained + state.mPrimalFuryRageGained + state.mPrimalMadnessRageGained + state.mEnrageAction.mRageGained + Math.round(state.mMobMeleeRageGained) + Math.round(state.mMobYellowRageGained) + Math.round(state.mMobMagicRageGained);
		report.setResult(BearThreatStat.RAGE_GAINED, rageGained);
		report.setResult(BearThreatStat.OOC_PROCS, state.mOoCEv.mNrOoCProcs);
		report.setResult(BearThreatStat.OOC_AVG_RAGE_RETURN, (state.mOoCEv.mNrOoCProcs > 0) ? (double) state.mOoCEv.mOoCRageReturn / state.mOoCEv.mNrOoCProcs : 0);
		double ooCTPS = (state.mOoCEv.mNrOoCProcs * 10 * state.mModel.multThreatStance) / encounterDuration;
		double rageGainTPS = ((state.mRageGainedCountThreatApplyMult * 5 * state.mModel.multThreatStance) + (state.mRageGainedCountThreat * 5)) / encounterDuration + ooCTPS;
		report.setResult(BearThreatStat.RAGE_GAIN_TPS, rageGainTPS);
		totalTPS += rageGainTPS;

		// White attack
		report.setResult(BearThreatStat.WHITE_ATTACK_DPS, state.mMeleeEv.mWhiteDamage / encounterDuration);
		long whiteAttacks = state.mMeleeEv.mNrWhiteMiss + state.mMeleeEv.mNrWhiteDodge + state.mMeleeEv.mNrWhiteParry + state.mMeleeEv.mNrWhiteGlance + state.mMeleeEv.mNrWhiteCrit + state.mMeleeEv.mNrWhiteHit;
		report.setResult(BearThreatStat.WHITE_ATTACKS, whiteAttacks);
		double whiteAttackTPS = 0;
		if (whiteAttacks > 0) {
			report.setResult(BearThreatStat.P_WHITE_ATTACK_MISS, (double) state.mMeleeEv.mNrWhiteMiss / whiteAttacks * 100);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_DODGE, (double) state.mMeleeEv.mNrWhiteDodge / whiteAttacks * 100);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_PARRY, (double) state.mMeleeEv.mNrWhiteParry / whiteAttacks * 100);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_GLANCE, (double) state.mMeleeEv.mNrWhiteGlance / whiteAttacks * 100);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_CRIT, (double) state.mMeleeEv.mNrWhiteCrit / whiteAttacks * 100);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_HIT, (double) state.mMeleeEv.mNrWhiteHit / whiteAttacks * 100);
			report.setResult(BearThreatStat.WHITE_ATTACK_INTERVAL, encounterDuration / whiteAttacks);
			report.setResult(BearThreatStat.WHITE_ATTACK_AVG_DMG, state.mMeleeEv.mWhiteDamage / whiteAttacks);
			whiteAttackTPS = (state.mMeleeEv.mWhiteDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_WHITE_ATTACK_MISS, 0);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_DODGE, 0);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_PARRY, 0);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_GLANCE, 0);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_CRIT, 0);
			report.setResult(BearThreatStat.P_WHITE_ATTACK_HIT, 0);
			report.setResult(BearThreatStat.WHITE_ATTACK_INTERVAL, 0);
			report.setResult(BearThreatStat.WHITE_ATTACK_AVG_DMG, 0);
		}
		report.setResult(BearThreatStat.WHITE_ATTACK_TPS, whiteAttackTPS);
		totalTPS += whiteAttackTPS;

		// Fury Swipes
		report.setResult(BearThreatStat.FURY_SWIPES_DPS, state.mMeleeEv.mFurySwipesDamage / encounterDuration);
		long furySwipes = state.mMeleeEv.mNrFurySwipesMiss + state.mMeleeEv.mNrFurySwipesDodge + state.mMeleeEv.mNrFurySwipesParry + state.mMeleeEv.mNrFurySwipesCrit + state.mMeleeEv.mNrFurySwipesHit;
		report.setResult(BearThreatStat.FURY_SWIPES, furySwipes);
		double furySwipesTPS = 0;
		if (furySwipes > 0) {
			report.setResult(BearThreatStat.P_FURY_SWIPES_MISS, (double) state.mMeleeEv.mNrFurySwipesMiss / furySwipes * 100);
			report.setResult(BearThreatStat.P_FURY_SWIPES_DODGE, (double) state.mMeleeEv.mNrFurySwipesDodge / furySwipes * 100);
			report.setResult(BearThreatStat.P_FURY_SWIPES_PARRY, (double) state.mMeleeEv.mNrFurySwipesParry / furySwipes * 100);
			report.setResult(BearThreatStat.P_FURY_SWIPES_CRIT, (double) state.mMeleeEv.mNrFurySwipesCrit / furySwipes * 100);
			report.setResult(BearThreatStat.P_FURY_SWIPES_HIT, (double) state.mMeleeEv.mNrFurySwipesHit / furySwipes * 100);
			report.setResult(BearThreatStat.FURY_SWIPES_AVG_DMG, state.mMeleeEv.mFurySwipesDamage / furySwipes);
			furySwipesTPS = (state.mMeleeEv.mFurySwipesDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_FURY_SWIPES_MISS, 0);
			report.setResult(BearThreatStat.P_FURY_SWIPES_DODGE, 0);
			report.setResult(BearThreatStat.P_FURY_SWIPES_PARRY, 0);
			report.setResult(BearThreatStat.P_FURY_SWIPES_CRIT, 0);
			report.setResult(BearThreatStat.P_FURY_SWIPES_HIT, 0);
			report.setResult(BearThreatStat.FURY_SWIPES_AVG_DMG, 0);
		}
		report.setResult(BearThreatStat.FURY_SWIPES_TPS, furySwipesTPS);
		totalTPS += furySwipesTPS;

		// Feral FF
		// XXX: This is currently primary target only.
		report.setResult(BearThreatStat.FERAL_FF_DPS, primaryTargetData.mFeralFFAction.mFFDamage / encounterDuration);
		long feralFFs = primaryTargetData.mFeralFFAction.mNrFFMiss + primaryTargetData.mFeralFFAction.mNrFFCrit + primaryTargetData.mFeralFFAction.mNrFFHit;
		report.setResult(BearThreatStat.FERAL_FFS, feralFFs);
		double feralFFTPS = 0;
		if (feralFFs > 0) {
			report.setResult(BearThreatStat.P_FERAL_FF_MISS, (double) primaryTargetData.mFeralFFAction.mNrFFMiss / feralFFs * 100);
			report.setResult(BearThreatStat.P_FERAL_FF_CRIT, (double) primaryTargetData.mFeralFFAction.mNrFFCrit / feralFFs * 100);
			report.setResult(BearThreatStat.P_FERAL_FF_HIT, (double) primaryTargetData.mFeralFFAction.mNrFFHit / feralFFs * 100);
			report.setResult(BearThreatStat.FERAL_FF_AVG_DMG, (double) primaryTargetData.mFeralFFAction.mFFDamage / feralFFs);
			int feralFFBonus = state.mModel.mLevelDep.getFeralFFFAThreat() * state.mModel.mToon.getTalent(BearModel.FERALAGGRESSION);
			report.setResult(BearThreatStat.FERAL_FF_AVG_THREAT, (feralFFBonus * (primaryTargetData.mFeralFFAction.mNrFFCrit + primaryTargetData.mFeralFFAction.mNrFFHit) + primaryTargetData.mFeralFFAction.mFFDamage) * state.mModel.multThreatStance / feralFFs);
			report.setResult(BearThreatStat.FERAL_FF_UPTIME, primaryTargetData.mFeralFFAction.getUpTime() / encounterDuration * 100);
			feralFFTPS = (feralFFBonus * (primaryTargetData.mFeralFFAction.mNrFFCrit + primaryTargetData.mFeralFFAction.mNrFFHit) + primaryTargetData.mFeralFFAction.mFFDamage) * state.mModel.multThreatStance / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_FERAL_FF_MISS, 0);
			report.setResult(BearThreatStat.P_FERAL_FF_CRIT, 0);
			report.setResult(BearThreatStat.P_FERAL_FF_HIT, 0);
			report.setResult(BearThreatStat.FERAL_FF_AVG_DMG, 0);
			report.setResult(BearThreatStat.FERAL_FF_AVG_THREAT, 0);
			report.setResult(BearThreatStat.FERAL_FF_UPTIME, 0);
		}
		report.setResult(BearThreatStat.FERAL_FF_TPS, feralFFTPS);
		totalTPS += feralFFTPS;

		// Mangle
		report.setResult(BearThreatStat.MANGLE_DPS, state.mMangleAction.mMangleDamage / encounterDuration);
		long mangles = state.mMangleAction.mNrMangleMiss + state.mMangleAction.mNrMangleDodge + state.mMangleAction.mNrMangleParry + state.mMangleAction.mNrMangleCrit + state.mMangleAction.mNrMangleHit;
		report.setResult(BearThreatStat.MANGLES, mangles);
		double mangleTPS = 0;
		if (mangles > 0) {
			report.setResult(BearThreatStat.P_MANGLE_MISS, (double) state.mMangleAction.mNrMangleMiss / mangles * 100);
			report.setResult(BearThreatStat.P_MANGLE_DODGE, (double) state.mMangleAction.mNrMangleDodge / mangles * 100);
			report.setResult(BearThreatStat.P_MANGLE_PARRY, (double) state.mMangleAction.mNrMangleParry / mangles * 100);
			report.setResult(BearThreatStat.P_MANGLE_CRIT, (double) state.mMangleAction.mNrMangleCrit / mangles * 100);
			report.setResult(BearThreatStat.P_MANGLE_HIT, (double) state.mMangleAction.mNrMangleHit / mangles * 100);
			report.setResult(BearThreatStat.MANGLE_AVG_DMG, state.mMangleAction.mMangleDamage / mangles);
			report.setResult(BearThreatStat.MANGLE_AVG_THREAT, state.mMangleAction.mMangleDamage * state.mModel.multThreatStance / mangles);
			report.setResult(BearThreatStat.MANGLE_UPTIME, state.mMangleAction.getUpTime() / encounterDuration * 100);

			report.setResult(BearThreatStat.BERSERK_UPTIME, state.mBerserkAction.getUpTime() / encounterDuration * 100);
			report.setResult(BearThreatStat.BERSERK_MANGLES, state.mMangleAction.mNrBerserkMangle);
			report.setResult(BearThreatStat.BERSERKS, state.mBerserkAction.mNrBerserk);
			report.setResult(BearThreatStat.BERSERK_PROCS, state.mBerserkProcEv.mNrBerserkProcs);
			report.setResult(BearThreatStat.BERSERK_PROC_MANGLES, state.mMangleAction.mNrBerserkProcMangle);
			report.setResult(BearThreatStat.BERSERK_PROCS_WASTED, state.mBerserkProcEv.mNrBerserkProcs - state.mMangleAction.mNrBerserkProcMangle);
			report.setResult(BearThreatStat.MANGLE_TPR, state.mMangleAction.mMangleDamage * state.mModel.multThreatStance / mangles / state.mModel.mangleCost);
			mangleTPS = (state.mMangleAction.mMangleDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_MANGLE_MISS, 0);
			report.setResult(BearThreatStat.P_MANGLE_DODGE, 0);
			report.setResult(BearThreatStat.P_MANGLE_PARRY, 0);
			report.setResult(BearThreatStat.P_MANGLE_CRIT, 0);
			report.setResult(BearThreatStat.P_MANGLE_HIT, 0);
			report.setResult(BearThreatStat.MANGLE_AVG_DMG, 0);
			report.setResult(BearThreatStat.MANGLE_AVG_THREAT, 0);
			report.setResult(BearThreatStat.MANGLE_UPTIME, 0);

			report.setResult(BearThreatStat.BERSERK_UPTIME, state.mBerserkAction.getUpTime() / encounterDuration * 100);
			report.setResult(BearThreatStat.BERSERK_MANGLES, 0);
			report.setResult(BearThreatStat.BERSERKS, state.mBerserkAction.mNrBerserk);
			report.setResult(BearThreatStat.BERSERK_PROCS, state.mBerserkProcEv.mNrBerserkProcs);
			report.setResult(BearThreatStat.BERSERK_PROC_MANGLES, 0);
			report.setResult(BearThreatStat.BERSERK_PROCS_WASTED, state.mBerserkProcEv.mNrBerserkProcs);
			report.setResult(BearThreatStat.MANGLE_TPR, 0);
		}
		report.setResult(BearThreatStat.MANGLE_TPS, mangleTPS);
		totalTPS += mangleTPS;

		// Lacerate
		report.setResult(BearThreatStat.LACERATE_DPS, (state.mLacerateAction.mLacerateDamage + state.mLacerateAction.mLacerateDoTDamage) / encounterDuration);
		report.setResult(BearThreatStat.LACERATE_DIRECT_DPS, state.mLacerateAction.mLacerateDamage / encounterDuration);
		report.setResult(BearThreatStat.LACERATE_DOT_DPS, state.mLacerateAction.mLacerateDoTDamage / encounterDuration);
		long lacerates = state.mLacerateAction.mNrLacerateMiss + state.mLacerateAction.mNrLacerateDodge + state.mLacerateAction.mNrLacerateParry + state.mLacerateAction.mNrLacerateCrit + state.mLacerateAction.mNrLacerateHit;
		report.setResult(BearThreatStat.LACERATES, lacerates);
		double lacerateTPS = 0;
		if (lacerates > 0) {
			report.setResult(BearThreatStat.P_LACERATE_MISS, (double) state.mLacerateAction.mNrLacerateMiss / lacerates * 100);
			report.setResult(BearThreatStat.P_LACERATE_DODGE, (double) state.mLacerateAction.mNrLacerateDodge / lacerates * 100);
			report.setResult(BearThreatStat.P_LACERATE_PARRY, (double) state.mLacerateAction.mNrLacerateParry / lacerates * 100);
			report.setResult(BearThreatStat.P_LACERATE_CRIT, (double) state.mLacerateAction.mNrLacerateCrit / lacerates * 100);
			report.setResult(BearThreatStat.P_LACERATE_HIT, (double) state.mLacerateAction.mNrLacerateHit / lacerates * 100);
			report.setResult(BearThreatStat.LACERATE_AVG_DMG, state.mLacerateAction.mLacerateDamage / lacerates);
			double lacerateAvgThreat = state.mLacerateAction.mLacerateDamage * state.mModel.multThreatStance / lacerates;
			report.setResult(BearThreatStat.LACERATE_AVG_THREAT, lacerateAvgThreat);
			long lacerateTicks = state.mLacerateAction.mNrLacerateDoTCrit + state.mLacerateAction.mNrLacerateDoTHit;
			report.setResult(BearThreatStat.LACERATE_TICKS, lacerateTicks);
			if (lacerateTicks > 0) {
				report.setResult(BearThreatStat.P_LACERATE_DOT_CRIT, (double) state.mLacerateAction.mNrLacerateDoTCrit / lacerateTicks * 100);
				report.setResult(BearThreatStat.P_LACERATE_DOT_HIT, (double) state.mLacerateAction.mNrLacerateDoTHit / lacerateTicks * 100);
				report.setResult(BearThreatStat.LACERATE_DOT_AVG_DMG, state.mLacerateAction.mLacerateDoTDamage / lacerateTicks);
				report.setResult(BearThreatStat.LACERATE_DOT_AVG_THREAT, state.mLacerateAction.mLacerateDoTDamage * state.mModel.multThreatStance / lacerateTicks);
			} else {
				// This should be extremely unlikely.
				report.setResult(BearThreatStat.P_LACERATE_DOT_CRIT, 0);
				report.setResult(BearThreatStat.P_LACERATE_DOT_HIT, 0);
				report.setResult(BearThreatStat.LACERATE_DOT_AVG_DMG, 0);
				report.setResult(BearThreatStat.LACERATE_DOT_AVG_THREAT, 0);
			}
			report.setResult(BearThreatStat.LACERATE_UPTIME, state.mLacerateAction.getUpTime() / encounterDuration * 100);
			report.setResult(BearThreatStat.LACERATE_TPR, lacerateAvgThreat / state.mModel.lacerateCost);
			report.setResult(BearThreatStat.LACERATE_DOT_TPS, (state.mLacerateAction.mLacerateDoTDamage * state.mModel.multThreatStance) / encounterDuration);
			report.setResult(BearThreatStat.LACERATE_DIRECT_TPS, (state.mLacerateAction.mLacerateDamage * state.mModel.multThreatStance) / encounterDuration);
			lacerateTPS = ((state.mLacerateAction.mLacerateDamage + state.mLacerateAction.mLacerateDoTDamage) * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_LACERATE_MISS, 0);
			report.setResult(BearThreatStat.P_LACERATE_DODGE, 0);
			report.setResult(BearThreatStat.P_LACERATE_PARRY, 0);
			report.setResult(BearThreatStat.P_LACERATE_CRIT, 0);
			report.setResult(BearThreatStat.P_LACERATE_HIT, 0);
			report.setResult(BearThreatStat.LACERATE_AVG_DMG, 0);
			report.setResult(BearThreatStat.LACERATE_AVG_THREAT, 0);
			report.setResult(BearThreatStat.LACERATE_TICKS, 0);
			report.setResult(BearThreatStat.P_LACERATE_DOT_CRIT, 0);
			report.setResult(BearThreatStat.P_LACERATE_DOT_HIT, 0);
			report.setResult(BearThreatStat.LACERATE_DOT_AVG_DMG, 0);
			report.setResult(BearThreatStat.LACERATE_DOT_AVG_THREAT, 0);
			report.setResult(BearThreatStat.LACERATE_UPTIME, 0);
			report.setResult(BearThreatStat.LACERATE_TPR, 0);
			report.setResult(BearThreatStat.LACERATE_DOT_TPS, 0);
			report.setResult(BearThreatStat.LACERATE_DIRECT_TPS, 0);
		}
		report.setResult(BearThreatStat.LACERATE_TPS, lacerateTPS);
		totalTPS += lacerateTPS;

		// Pulverize
		report.setResult(BearThreatStat.PULVERIZE_DPS, state.mPulverizeAction.mPulverizeDamage / encounterDuration);
		long pulverizes = state.mPulverizeAction.mNrPulverizeMiss + state.mPulverizeAction.mNrPulverizeDodge + state.mPulverizeAction.mNrPulverizeParry + state.mPulverizeAction.mNrPulverizeCrit + state.mPulverizeAction.mNrPulverizeHit;
		report.setResult(BearThreatStat.PULVERIZES, pulverizes);
		double pulverizeTPS = 0;
		if (pulverizes > 0) {
			report.setResult(BearThreatStat.P_PULVERIZE_MISS, (double) state.mPulverizeAction.mNrPulverizeMiss / pulverizes * 100);
			report.setResult(BearThreatStat.P_PULVERIZE_DODGE, (double) state.mPulverizeAction.mNrPulverizeDodge / pulverizes * 100);
			report.setResult(BearThreatStat.P_PULVERIZE_PARRY, (double) state.mPulverizeAction.mNrPulverizeParry / pulverizes * 100);
			report.setResult(BearThreatStat.P_PULVERIZE_CRIT, (double) state.mPulverizeAction.mNrPulverizeCrit / pulverizes * 100);
			report.setResult(BearThreatStat.P_PULVERIZE_HIT, (double) state.mPulverizeAction.mNrPulverizeHit / pulverizes * 100);
			report.setResult(BearThreatStat.PULVERIZE_AVG_DMG, state.mPulverizeAction.mPulverizeDamage / pulverizes);
			double pulverizeAvgThreat = state.mPulverizeAction.mPulverizeDamage * state.mModel.multThreatStance / pulverizes;
			report.setResult(BearThreatStat.PULVERIZE_AVG_THREAT, pulverizeAvgThreat);
			report.setResult(BearThreatStat.PULVERIZE_UPTIME, state.mPulverizeAction.getUpTime() / encounterDuration * 100);
			report.setResult(BearThreatStat.PULVERIZE_TPR, pulverizeAvgThreat / state.mModel.pulverizeCost);
			pulverizeTPS = (state.mPulverizeAction.mPulverizeDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_PULVERIZE_MISS, 0);
			report.setResult(BearThreatStat.P_PULVERIZE_DODGE, 0);
			report.setResult(BearThreatStat.P_PULVERIZE_PARRY, 0);
			report.setResult(BearThreatStat.P_PULVERIZE_CRIT, 0);
			report.setResult(BearThreatStat.P_PULVERIZE_HIT, 0);
			report.setResult(BearThreatStat.PULVERIZE_AVG_DMG, 0);
			report.setResult(BearThreatStat.PULVERIZE_AVG_THREAT, 0);
			report.setResult(BearThreatStat.PULVERIZE_UPTIME, 0);
			report.setResult(BearThreatStat.PULVERIZE_TPR, 0);
		}
		report.setResult(BearThreatStat.PULVERIZE_TPS, pulverizeTPS);
		totalTPS += pulverizeTPS;

		// Maul
		report.setResult(BearThreatStat.MAUL_DPS, state.mMaulAction.mMaulDamage / encounterDuration);
		long mauls = state.mMaulAction.mNrMaulMiss + state.mMaulAction.mNrMaulDodge + state.mMaulAction.mNrMaulParry + state.mMaulAction.mNrMaulCrit + state.mMaulAction.mNrMaulHit;
		report.setResult(BearThreatStat.MAULS, mauls);
		double maulTPS = 0;
		if (mauls > 0) {
			report.setResult(BearThreatStat.P_MAUL_MISS, (double) state.mMaulAction.mNrMaulMiss / mauls * 100);
			report.setResult(BearThreatStat.P_MAUL_DODGE, (double) state.mMaulAction.mNrMaulDodge / mauls * 100);
			report.setResult(BearThreatStat.P_MAUL_PARRY, (double) state.mMaulAction.mNrMaulParry / mauls * 100);
			report.setResult(BearThreatStat.P_MAUL_CRIT, (double) state.mMaulAction.mNrMaulCrit / mauls * 100);
			report.setResult(BearThreatStat.P_MAUL_HIT, (double) state.mMaulAction.mNrMaulHit / mauls * 100);
			report.setResult(BearThreatStat.MAUL_AVG_DMG, state.mMaulAction.mMaulDamage / mauls);
			int maulBonus = state.mModel.mLevelDep.getMaulBonusThreat();
			double maulAvgThreat = (maulBonus * (state.mMaulAction.mNrMaulCrit + state.mMaulAction.mNrMaulHit) + state.mMaulAction.mMaulDamage) * state.mModel.multThreatStance / mauls;
			report.setResult(BearThreatStat.MAUL_AVG_THREAT, maulAvgThreat);
			report.setResult(BearThreatStat.MAUL_TPR, maulAvgThreat / state.mModel.maulCost);
			maulTPS = ((maulBonus * (state.mMaulAction.mNrMaulCrit + state.mMaulAction.mNrMaulHit) + state.mMaulAction.mMaulDamage) * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_MAUL_MISS, 0);
			report.setResult(BearThreatStat.P_MAUL_DODGE, 0);
			report.setResult(BearThreatStat.P_MAUL_PARRY, 0);
			report.setResult(BearThreatStat.P_MAUL_CRIT, 0);
			report.setResult(BearThreatStat.P_MAUL_HIT, 0);
			report.setResult(BearThreatStat.MAUL_AVG_DMG, 0);
			report.setResult(BearThreatStat.MAUL_AVG_THREAT, 0);
			report.setResult(BearThreatStat.MAUL_TPR, 0);
		}
		report.setResult(BearThreatStat.MAUL_TPS, maulTPS);
		totalTPS += maulTPS;

		// Swipe
		report.setResult(BearThreatStat.SWIPE_DPS, state.mSwipeAction.mSwipeDamage / encounterDuration);
		long swipes = state.mSwipeAction.mNrSwipeMiss + state.mSwipeAction.mNrSwipeDodge + state.mSwipeAction.mNrSwipeParry + state.mSwipeAction.mNrSwipeCrit + state.mSwipeAction.mNrSwipeHit;
		report.setResult(BearThreatStat.SWIPES, swipes);
		double swipeTPS = 0;
		if (swipes > 0) {
			report.setResult(BearThreatStat.P_SWIPE_MISS, (double) state.mSwipeAction.mNrSwipeMiss / swipes * 100);
			report.setResult(BearThreatStat.P_SWIPE_DODGE, (double) state.mSwipeAction.mNrSwipeDodge / swipes * 100);
			report.setResult(BearThreatStat.P_SWIPE_PARRY, (double) state.mSwipeAction.mNrSwipeParry / swipes * 100);
			report.setResult(BearThreatStat.P_SWIPE_CRIT, (double) state.mSwipeAction.mNrSwipeCrit / swipes * 100);
			report.setResult(BearThreatStat.P_SWIPE_HIT, (double) state.mSwipeAction.mNrSwipeHit / swipes * 100);
			report.setResult(BearThreatStat.SWIPE_AVG_DMG, state.mSwipeAction.mSwipeDamage / swipes);
			double swipeAvgThreat = state.mSwipeAction.mSwipeDamage * state.mModel.multThreatStance / swipes;
			report.setResult(BearThreatStat.SWIPE_AVG_THREAT, swipeAvgThreat);
			report.setResult(BearThreatStat.SWIPE_TPR, swipeAvgThreat / state.mModel.swipeCost);
			swipeTPS = (state.mSwipeAction.mSwipeDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_SWIPE_MISS, 0);
			report.setResult(BearThreatStat.P_SWIPE_DODGE, 0);
			report.setResult(BearThreatStat.P_SWIPE_PARRY, 0);
			report.setResult(BearThreatStat.P_SWIPE_CRIT, 0);
			report.setResult(BearThreatStat.P_SWIPE_HIT, 0);
			report.setResult(BearThreatStat.SWIPE_AVG_DMG, 0);
			report.setResult(BearThreatStat.SWIPE_AVG_THREAT, 0);
			report.setResult(BearThreatStat.SWIPE_TPR, 0);
		}
		report.setResult(BearThreatStat.SWIPE_TPS, swipeTPS);
		totalTPS += swipeTPS;

		// Thrash
		report.setResult(BearThreatStat.THRASH_DPS, (state.mThrashAction.mThrashDamage + state.mThrashAction.mThrashDoTDamage) / encounterDuration);
		report.setResult(BearThreatStat.THRASH_DIRECT_DPS, state.mThrashAction.mThrashDamage / encounterDuration);
		report.setResult(BearThreatStat.THRASH_DOT_DPS, state.mThrashAction.mThrashDoTDamage / encounterDuration);
		long thrashs = state.mThrashAction.mNrThrashMiss + state.mThrashAction.mNrThrashDodge + state.mThrashAction.mNrThrashParry + state.mThrashAction.mNrThrashCrit + state.mThrashAction.mNrThrashHit;
		report.setResult(BearThreatStat.THRASHS, thrashs);
		double thrashTPS = 0;
		if (thrashs > 0) {
			report.setResult(BearThreatStat.P_THRASH_MISS, (double) state.mThrashAction.mNrThrashMiss / thrashs * 100);
			report.setResult(BearThreatStat.P_THRASH_DODGE, (double) state.mThrashAction.mNrThrashDodge / thrashs * 100);
			report.setResult(BearThreatStat.P_THRASH_PARRY, (double) state.mThrashAction.mNrThrashParry / thrashs * 100);
			report.setResult(BearThreatStat.P_THRASH_CRIT, (double) state.mThrashAction.mNrThrashCrit / thrashs * 100);
			report.setResult(BearThreatStat.P_THRASH_HIT, (double) state.mThrashAction.mNrThrashHit / thrashs * 100);
			report.setResult(BearThreatStat.THRASH_AVG_DMG, state.mThrashAction.mThrashDamage / thrashs);
			double thrashAvgThreat = state.mThrashAction.mThrashDamage * state.mModel.multThreatStance / thrashs;
			report.setResult(BearThreatStat.THRASH_AVG_THREAT, thrashAvgThreat);
			long thrashTicks = state.mThrashAction.mNrThrashDoTCrit + state.mThrashAction.mNrThrashDoTHit;
			report.setResult(BearThreatStat.THRASH_TICKS, thrashTicks);
			if (thrashTicks > 0) {
				report.setResult(BearThreatStat.P_THRASH_DOT_CRIT, (double) state.mThrashAction.mNrThrashDoTCrit / thrashTicks * 100);
				report.setResult(BearThreatStat.P_THRASH_DOT_HIT, (double) state.mThrashAction.mNrThrashDoTHit / thrashTicks * 100);
				report.setResult(BearThreatStat.THRASH_DOT_AVG_DMG, state.mThrashAction.mThrashDoTDamage / thrashTicks);
				report.setResult(BearThreatStat.THRASH_DOT_AVG_THREAT, state.mThrashAction.mThrashDoTDamage * state.mModel.multThreatStance / thrashTicks);
			} else {
				// This should be extremely unlikely.
				report.setResult(BearThreatStat.P_THRASH_DOT_CRIT, 0);
				report.setResult(BearThreatStat.P_THRASH_DOT_HIT, 0);
				report.setResult(BearThreatStat.THRASH_DOT_AVG_DMG, 0);
				report.setResult(BearThreatStat.THRASH_DOT_AVG_THREAT, 0);
			}
			report.setResult(BearThreatStat.THRASH_UPTIME, state.mThrashAction.getUpTime() / encounterDuration * 100);
			report.setResult(BearThreatStat.THRASH_TPR, thrashAvgThreat / state.mModel.thrashCost);
			report.setResult(BearThreatStat.THRASH_DOT_TPS, (state.mThrashAction.mThrashDamage * state.mModel.multThreatStance) / encounterDuration);
			report.setResult(BearThreatStat.THRASH_DIRECT_TPS, (state.mThrashAction.mThrashDamage * state.mModel.multThreatStance) / encounterDuration);
			thrashTPS = ((state.mThrashAction.mThrashDamage + state.mThrashAction.mThrashDoTDamage) * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.P_THRASH_MISS, 0);
			report.setResult(BearThreatStat.P_THRASH_DODGE, 0);
			report.setResult(BearThreatStat.P_THRASH_PARRY, 0);
			report.setResult(BearThreatStat.P_THRASH_CRIT, 0);
			report.setResult(BearThreatStat.P_THRASH_HIT, 0);
			report.setResult(BearThreatStat.THRASH_AVG_DMG, 0);
			report.setResult(BearThreatStat.THRASH_AVG_THREAT, 0);
			report.setResult(BearThreatStat.THRASH_TICKS, 0);
			report.setResult(BearThreatStat.P_THRASH_DOT_CRIT, 0);
			report.setResult(BearThreatStat.P_THRASH_DOT_HIT, 0);
			report.setResult(BearThreatStat.THRASH_DOT_AVG_DMG, 0);
			report.setResult(BearThreatStat.THRASH_DOT_AVG_THREAT, 0);
			report.setResult(BearThreatStat.THRASH_UPTIME, 0);
			report.setResult(BearThreatStat.THRASH_TPR, 0);
			report.setResult(BearThreatStat.THRASH_DOT_TPS, 0);
			report.setResult(BearThreatStat.THRASH_DIRECT_TPS, 0);
		}
		report.setResult(BearThreatStat.THRASH_TPS, thrashTPS);
		totalTPS += thrashTPS;

		// Fiery Claws (Tier 12 2pc)
		report.setResult(BearThreatStat.FIERY_CLAWS_DPS, (double) state.mTier_12_2pcEv.mFieryClawsDamage / encounterDuration);
		int nrFieryClawsTicks = state.mTier_12_2pcEv.mNrFieryClawsTicks;
		report.setResult(BearThreatStat.FIERY_CLAWS_TICKS, nrFieryClawsTicks);
		double fieryClawsTPS = 0;
		if (nrFieryClawsTicks > 0) {
			report.setResult(BearThreatStat.FIERY_CLAWS_AVG_DMG, (double) state.mTier_12_2pcEv.mFieryClawsDamage / nrFieryClawsTicks);
			report.setResult(BearThreatStat.FIERY_CLAWS_ROLLED, state.mTier_12_2pcEv.mNrFieryClawsRolled);
			report.setResult(BearThreatStat.FIERY_CLAWS_ROLLED_DMG, state.mTier_12_2pcEv.mFieryClawsDmgRolled);
			report.setResult(BearThreatStat.FIERY_CLAWS_MUNCHED, state.mTier_12_2pcEv.mNrFieryClawsMunch);
			report.setResult(BearThreatStat.FIERY_CLAWS_MUNCHED_DMG, state.mTier_12_2pcEv.mFieryClawsDmgLostMunch);
			report.setResult(BearThreatStat.FIERY_CLAWS_REFRESH_LOST, state.mTier_12_2pcEv.mNrFieryClawsRefreshLost);
			report.setResult(BearThreatStat.FIERY_CLAWS_REFRESH_LOST_DMG, state.mTier_12_2pcEv.mFieryClawsDmgLostRefresh);
			double fieryClawsAvgThreat = (state.mTier_12_2pcEv.mFieryClawsDamage * state.mModel.multThreatStance) / nrFieryClawsTicks;
			report.setResult(BearThreatStat.FIERY_CLAWS_AVG_THREAT, fieryClawsAvgThreat);
			fieryClawsTPS = (state.mTier_12_2pcEv.mFieryClawsDamage * state.mModel.multThreatStance) / encounterDuration;
		} else {
			report.setResult(BearThreatStat.FIERY_CLAWS_AVG_DMG, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_AVG_THREAT, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_ROLLED, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_ROLLED_DMG, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_MUNCHED, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_MUNCHED_DMG, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_REFRESH_LOST, 0);
			report.setResult(BearThreatStat.FIERY_CLAWS_REFRESH_LOST_DMG, 0);
		}
		report.setResult(BearThreatStat.FIERY_CLAWS_TPS, fieryClawsTPS);
		totalTPS += fieryClawsTPS;

		// Other sources
		double otherTPS = (state.mProcPhysDamage + state.mProcSpellDamage) * state.mModel.multThreatStance / encounterDuration + demoralizingRoarTPS + frenziedRegenTPS;
		report.setResult(BearThreatStat.OTHER_DPS, (state.mProcPhysDamage + state.mProcSpellDamage) / encounterDuration);
		report.setResult(BearThreatStat.OTHER_TPS, otherTPS);
		totalTPS += otherTPS;

		report.mProcUptimes = new HashMap<ProcEffect, Double>();
		for (SimulationProc proc : state.mSimEngine.getProcs()) {
			ProcEffect procEffect = proc.getProcEffect();
			if (procEffect.buffDuration == 0)
				report.mProcUptimes.put(proc.getProcEffect(), (double) proc.mNrProcs);
			else
				report.mProcUptimes.put(proc.getProcEffect(), proc.getUpTime() / state.mSimEngine.getEndTime());
		}

		// Work out total damage for debugging/DPS calculations.
		long whiteDmg = state.mMeleeEv.mWhiteDamage;
		long furySwipesDmg = state.mMeleeEv.mFurySwipesDamage;
		long feralFFDmg = primaryTargetData.mFeralFFAction.mFFDamage;
		long lacerateDmg = state.mLacerateAction.mLacerateDamage;
		long lacerateDoTDmg = state.mLacerateAction.mLacerateDoTDamage;
		long mangleDmg = state.mMangleAction.mMangleDamage;
		long maulDmg = state.mMaulAction.mMaulDamage;
		long pulverizeDmg = state.mPulverizeAction.mPulverizeDamage;
		long thrashDmg = state.mThrashAction.mThrashDamage;
		long thrashDoTDmg = state.mThrashAction.mThrashDoTDamage;
		long swipeDmg = state.mSwipeAction.mSwipeDamage;
		long fieryClawsDmg = state.mTier_12_2pcEv.mFieryClawsDamage;
		long otherDmg = state.mProcPhysDamage + state.mProcSpellDamage;
		long totalDamage = whiteDmg + furySwipesDmg + feralFFDmg + lacerateDmg + lacerateDoTDmg + mangleDmg + maulDmg + pulverizeDmg + thrashDmg + thrashDoTDmg + swipeDmg + fieryClawsDmg + otherDmg;

		double dps = totalDamage / encounterDuration;
		double totalDamageTaken = state.mMobWhiteDamageTaken + state.mMobYellowDamageTaken + state.mMobMagicDamageTaken;

		report.setResult(BearThreatStat.TOTAL_DAMAGE, totalDamage);
		report.setResult(BearThreatStat.TOTAL_DAMAGE_TAKEN, totalDamageTaken);

		report.setResult(Stat.THREAT_PER_SECOND, totalTPS);
		report.setResult(Stat.DAMAGE_DEALT_PER_SECOND, dps);

		// Derive TPS breakdowns.
		report.setResult(BearThreatStat.P_RAGE_TPS, rageGainTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_WHITE_ATTACK_TPS, whiteAttackTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_FURY_SWIPES_TPS, furySwipesTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_FERAL_FF_TPS, feralFFTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_MANGLE_TPS, mangleTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_MAUL_TPS, maulTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_LACERATE_TPS, lacerateTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_PULVERIZE_TPS, pulverizeTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_THRASH_TPS, thrashTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_SWIPE_TPS, swipeTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_FIERY_CLAWS_TPS, fieryClawsTPS / totalTPS * 100);
		report.setResult(BearThreatStat.P_OTHER_TPS, otherTPS / totalTPS * 100);

		// Calculate total mitigation to allow for reporting stat values around that in the future.
		// Note: This requires *a lot* of iterations to gain valid results.
		double mitigationValue = 0;
		if (state.mMobWhiteBaseDamage + state.mMobYellowBaseDamage + state.mMobMagicBaseDamage > 0) {
			double totalBaseDamage = state.mMobWhiteBaseDamage + state.mMobYellowBaseDamage + state.mMobMagicBaseDamage;
			mitigationValue = 1 / (1 - (1 - totalDamageTaken / totalBaseDamage));
			report.setResult(BearThreatStat.TOTAL_DAMAGE_BASE, totalBaseDamage);
		}
		report.setResult(Stat.MITIGATION_VALUE, mitigationValue);

		report.mShouldReportMitigation = state.mSimEngine.getConfig().getBool(SimulatorProperty.REPORTMIT);

		if (report.mDumpVengeance || report.mDumpWhiteDamage || report.mDumpYellowDamage || report.mDumpMagicDamage) {
			report.mConfig = state.mSimEngine.getConfig().toString();
			if (report.mDumpVengeance) report.mVengeanceSamples = state.mVengeanceEv.mVengeanceSamples;
			if (report.mDumpWhiteDamage && state.mMobWhiteDamageTakenSamples != null) {
				int i = 0;
				report.mWhiteDamageSamples = new double[state.mMobWhiteDamageTakenSamples.size()];
				for (Double val : state.mMobWhiteDamageTakenSamples) report.mWhiteDamageSamples[i++] = val;
			}
			if (report.mDumpYellowDamage && state.mMobYellowDamageTakenSamples != null) {
				int i = 0;
				report.mYellowDamageSamples = new double[state.mMobYellowDamageTakenSamples.size()];
				for (Double val : state.mMobYellowDamageTakenSamples) report.mYellowDamageSamples[i++] = val;
			}
			if (report.mDumpMagicDamage && state.mMobMagicDamageTakenSamples != null) {
				int i = 0;
				report.mMagicDamageSamples = new double[state.mMobMagicDamageTakenSamples.size()];
				for (Double val : state.mMobMagicDamageTakenSamples) report.mMagicDamageSamples[i++] = val;
			}
		}

		return report;
	}

	@Override
	public String getVersionInfo() {
		return VERSION_INFO;
	}

	@Override
	public Class<?>[] getExportClasses() {
		return new Class<?>[] {
				Action.class,
				BearThreatSimulator.class
		};
	}

	@Override
	public boolean canPerformAction(IStatus state, IAction action) {
		// The bear model can always perform all actions. (GCD checking done elsewhere.)
		return true;
	}

	@Override
	public boolean shouldQueryStrategy(IStatus state) {
		// The bear model is always on target, so there's never any time to not query the strategy.
		return true;
	}

	@Override
	public SimulationTarget getActiveTarget(IStatus state) {
		return ((BearThreatSimulator) state).mActiveTarget;
	}
}
